#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>

#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUF_SIZE 4096

#define set_fl(fd, fl)                           \
    if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) < 0) \
    {                                            \
        perror("fcntl fset fl");                 \
        exit(1);                                 \
    } 

enum
{
    STATE_R = 1,
    STATE_W,
    STATE_EX,
    STATE_T
};

struct fsm_st
{
    int state;
    int src;
    int dst;
    char buf[BUF_SIZE];
    ssize_t buf_len;
    char* cursor;
};

static void fsm_driver(struct fsm_st* fsm) {
    switch (fsm->state)
    {
    case STATE_R: {
        // read content into buffer
        ssize_t len = read(fsm->src, fsm->buf, BUF_SIZE);
        if(len < 0) {
            // error occurred, if error is err int or err nonblock, do nothing
            if(errno == EAGAIN) {
                break;
            }
            fsm->state = STATE_EX;
            break;
        }
        if(len == 0) {
            // terminated
            fsm->state = STATE_T;
            break;
        }

        fsm->buf_len = len;
        fsm->state = STATE_W;
        fsm->cursor = fsm->buf;
        break;        
    }

    case STATE_W: {
        ssize_t len = write(fsm->dst, fsm->cursor, fsm->buf_len);
        if (len < 0) {
            // error occurred, if error is err int or err nonblock, do nothing
            if(errno == EAGAIN) {
                break;
            }
            fsm->state = STATE_EX;
            break;            
        }

        fsm->buf_len -= len;
        fsm->cursor += len;
        if(fsm->buf_len == 0) {
            fsm->state = STATE_R;
        }
        break;
    }
    case STATE_EX: {
        perror("state ex");
        close(fsm->src);
        close(fsm->dst);
        exit(1);
        break;
    } 
    
    default:
        break;
    }
}

static void relay(int fd1, int fd2)
{
    // set NONBLOCK flag
    int fd1_sv = fcntl(fd1, F_GETFL);
    int fd2_sv = fcntl(fd2, F_GETFL);

    set_fl(fd1, fd1_sv | O_NONBLOCK);
    set_fl(fd2, fd2_sv | O_NONBLOCK);

    struct fsm_st fsm12;
    struct fsm_st fsm21;

    fsm12.src = fd1;
    fsm12.dst = fd2;
    fsm12.state = STATE_R;

    fsm21.src = fd2;
    fsm21.dst = fd1;
    fsm21.state = STATE_R;

    while(fsm12.state != STATE_T && fsm21.state != STATE_T) {
        fsm_driver(&fsm12);
        fsm_driver(&fsm21);
    }


    set_fl(fd1, fd1_sv);
    set_fl(fd2, fd2_sv);
}

// copy based on fsm
static void copy(int src, int dst) {    
    struct fsm_st fsm;

    fsm.src = src;
    fsm.dst = dst;
    fsm.state = STATE_R;

    while(fsm.state != STATE_T) {
        fsm_driver(&fsm);
    }

    close(src);
    close(dst);
}

int main(int argc, char** argv) {
    if(argc < 3) {
        fprintf(stderr, "copy; Usage: %s source_file dst_file\n", argv[0]);
        exit(1);
    }

    int src = open(argv[1], O_RDONLY | O_NONBLOCK);

    if(src < 0) {
        perror("open()");
        exit(1);
    }

    int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, 0755);
    if(dst < 0) {
        perror("open()");
        exit(1);
    }
    
    copy(src, dst);
    exit(0);
}